<?php

namespace app\index\service;

use \think\Config;
use app\common\model\jooj_member\MemberFace;
use app\common\model\jooj_member\MemberFaceGrant;
use app\common\model\jooj_member\MemberIndex;
use app\common\model\jooj_member\MemberFaceArrive;
use app\common\model\oms\MemberSign;
use app\common\common\Constants;
use app\common\service\BaiduService;
use app\common\service\OssService;
use app\common\model\oms\StoreOptometryDeviceSn as StoreOptometryDeviceSnModel;

/**
 * 百度人脸识别相关
 *
 * @author jiang.ding
 */
class FaceRecognitionService {

    /**
     * 保存集团ID
     */
    private $_companyId;
    private $_ossServiceGrant;
    private $_baiduService;
    private $_sodSnModel;   //设备SN表

    /**
     * 初始化当前类,根据当前集团ID创建分表
     * @param type $companyId 当前集团ID
     * @return boolean 
     */
    public function __construct($companyId) {
        $this->_companyId = $companyId;

        if (true === empty($this->_companyId)) {
            return false;
        }

        # 创建model对象 
        $this->_sodSnModel = new StoreOptometryDeviceSnModel();

        # 创建百度接口对象,创建Oss的授权bucket对象
        $this->_baiduService = new BaiduService();
        $this->_ossServiceGrant = new OssService(Config::get('ALIYUN_OSS_FACE_GRANT_BUCKET'));

    }

    /**
     * 保存百度face信息
     * @param $data array 添加的数据
     * @return object 添加数据的结果集
     */
    public function addMemberFace($data) {

        $memberFaceModel = new MemberFace($this->_companyId);
        $saveData = [
        'member_code' => $data['member_code'],
        'arrive_time' => $data['arrive_time'],
        'pic_url' => $data['pic_url'],
        'group_id' => $data['group_id'],
        'come_from' => $data['come_from'],
        'create_time' => date('Y-m-d H:i:s', time()),
        'gender' => $data['gender'],
        'gender_probability' => $data['gender_probability'],
        'age' => $data['age'],
        'expression' => $data['expression'],
        'expression_probability' => $data['expression_probablity'], //注意:百度返回的是probablity
        'glasses' => $data['glasses'],
        'glasses_probability' => $data['glasses_probability'],
        'race' => $data['race'],
        'race_probability' => $data['race_probability'],
        ## 位置和宽高
        'location_left' => $data['location']['left'],
        'location_top' => $data['location']['top'],
        'location_width' => $data['location']['width'],
        'location_height' => $data['location']['height'],
        'face_probability' => $data['face_probability'],
        ##立体角度
        'rotation_angle' => $data['rotation_angle'],
        'yaw' => $data['yaw'],
        'pitch' => $data['pitch'],
        'roll' => $data['roll'],
        ##四点坐标
        'landmark_left_eye_x' => $data['landmark'][0]['x'],
        'landmark_left_eye_y' => $data['landmark'][0]['y'],
        'landmark_right_eye_x' => $data['landmark'][1]['x'],
        'landmark_right_eye_y' => $data['landmark'][1]['y'],
        'landmark_nose_x' => $data['landmark'][2]['x'],
        'landmark_nose_y' => $data['landmark'][2]['y'],
        'landmark_mouth_x' => $data['landmark'][3]['x'],
        'landmark_mouth_y' => $data['landmark'][3]['y'],
        ##颜值和脸型
        'beauty' => $data['beauty'],
        'faceshape_square_probability' => $data['faceshape'][0]['probability'],
        'faceshape_triangle_probability' => $data['faceshape'][1]['probability'],
        'faceshape_oval_probability' => $data['faceshape'][2]['probability'],
        'faceshape_heart_probability' => $data['faceshape'][3]['probability'],
        'faceshape_round_probability' => $data['faceshape'][4]['probability'],
        ##人脸质量
        ### 五官遮挡程度
        'qualities_occlusion_left_eye' => $data['qualities']['occlusion']['left_eye'],
        'qualities_occlusion_right_eye' => $data['qualities']['occlusion']['right_eye'],
        'qualities_occlusion_nose' => $data['qualities']['occlusion']['nose'],
        'qualities_occlusion_mouth' => $data['qualities']['occlusion']['mouth'],
        'qualities_occlusion_left_cheek' => $data['qualities']['occlusion']['left_cheek'],
        'qualities_occlusion_right_cheek' => $data['qualities']['occlusion']['right_cheek'],
        'qualities_occlusion_chin' => $data['qualities']['occlusion']['chin'],
        ###模糊度,光照度,完整度
        'qualities_blur' => $data['qualities']['blur'],
        'qualities_illumination' => $data['qualities']['illumination'],
        'qualities_completeness' => $data['qualities']['completeness'],
        'qualities_type_human' => $data['qualities']['type']['human'],
        'qualities_type_cartoon' => $data['qualities']['type']['cartoon'],
        ];
        return $memberFaceModel->create($saveData);
    }

    /**
     * 查询member_index表的信息
     * @param $condition array 查询条件
     * @param $isOne bool 查询条数是否为一条
     */
    public static function getMemberIndexInfo($condition, $isOne = true) {

        if ($isOne) {
            $result = MemberFace::get($condition);
        } else {
            $result = MemberFace::all($condition);
        }
        return $result;
    }

    /**
     * 添加会员授权的图片
     * @param type $data array
     * @return int (会员)授权图片表的主键ID
     */
    public function addMemberGrantPic($data) {

        $grant = new MemberFaceGrant($this->_companyId);
        $grantRes = $grant->create($data);
        return $grantRes->grant_id;
    }

    /**
     * 获取会员授权图片的信息
     * @param $memberCode 集团会员ID
     * @return object 查询结果集
     */
    public function getMemberGrantInfo($where) {

        $grant = new MemberFaceGrant($this->_companyId);
//        $grantInfoRes = $grant->where(['member_code' => $where['memberCode']])->order('create_time','desc')->find();  //只取时间最大的那一条
        $grantInfoRes = $grant->where(['member_code' => $where['member_code'], 'grant_id' => $where['grant_id']])->find();  //人与图片一一对应
        return $grantInfoRes;
    }

    /**
     * 添加到店人脸图片的记录
     * @param type $data array
     */
    public function addMemberArrivePic($data) {

        $arrive = new MemberFaceArrive($this->_companyId);
//        $arriveRes = $arrive->create($data)->toArray();
        $arriveRes = $arrive->create($data);
        return $arriveRes;
    }

    /**
     * 查询到店人脸信息
     * @param $data array 查询条件
     * @return 查询当天的结果集
     */
    public function getMemberArriveInfo($data) {

        # 查询当天是否到店
        $today = date('Y-m-d H:i:s', strtotime(date('Y-m-d')));
        $nextDay = date('Y-m-d H:i:s',
                strtotime(date('Y-m-d', time() + 3600 * 24)));
        $arrive = new MemberFaceArrive($this->_companyId);
        $arriveInfoRes = $arrive->where(['member_code' => $data['member_code'], 'store_id' => $data['store_id']])->where('create_time',
                        'between time', [$today, $nextDay])->find();

        return $arriveInfoRes;
    }

    /**
     * 添加潜客记录
     * @param $data array 添加数据
     * @return object 返回创建的结果对象 
     */
    public function addMemberIndex($data) {

        $index = new MemberIndex($this->_companyId);
//        $indexRes = $index->create($data)->toArray();
        $indexRes = $index->create($data);
        // return $indexRes->member_code;
        return $indexRes;
    }

    /**
     * 修改潜客记录
     * @param $data array 修改的数据
     * @param $where array 修改的条件
     * @return $indexRes  object 受影响的行数
     */
    public function updateMemberIndex($data, $where) {

        $index = new MemberIndex($this->_companyId);
        $indexRes = $index->update($data, $where);
        return $indexRes;
    }

    /**
     * 修改member_face记录
     * @param $data array 修改的数据
     * @param $where array 修改的条件
     * @return $faceRes obejct 受影响的行数
     */
    public function updateMemberFace($data, $where) {

        $face = new MemberFace($this->_companyId);
//        $faceRes = $face->update($data, $where);  //包含软删除数据
        $faceRes = $face->where($where)->update($data);
        return $faceRes;
    }

    //+---------------------------------------------+
    //+--    下列方法用于OMS接口(添加授权在上面)     --+ 
    //+---------------------------------------------+

    /**
     * 解绑会员授权信息(解绑当前会员所有授权图片)  [OK]
     * @param $memberCode string 集团会员ID
     * @return $cancelGrantRes type bool 成功返回true,失败返回false
     */
    public function cancelMemberGrantInfo($memberCode) {

        $grant = new MemberFaceGrant($this->_companyId);
//        $grant->restore(['grant_id'=>42]);die;  //恢复软删除数据(pk)
        # 获取当前会员所有的member_code(数组)
        $member = new MemberIndex($this->_companyId);
        $memberCodeArr = $member->getMemberCodeArrByMemberCode($memberCode);

        # 获取当前会员所有的授权图片信息
        $grantObj = $grant->where('member_code', 'in', $memberCodeArr)->select();
        $grantArr = collection($grantObj)->toArray();

        if (true === empty($grantObj)) {
            throw new \Exception('该会员的授权信息不存在');
        }

        foreach ($grantArr as $val) {

            # 删除百度人脸库对应图片
            $uid = $val['member_code'] . '_' . $val['grant_id']; //member_code拼接grant_id
            $options['group_id'] = Config::get('BAIDU_FACE_GRANT_GROUP');
            $deleteUserRes = $this->_baiduService->deleteUser($uid, $options);

            ## 百度人脸删除错误信息
            if (false === empty($deleteUserRes['error_code'])) {
                throw new \Exception($deleteUserRes['error_msg']);
            }

            # 软删除当前授权图片记录
            $grantDel = $grant->where(['member_code' => $val['member_code'], 'grant_id' => $val['grant_id']])->find();
            $cancelGrantPic = $grantDel->delete();

            if (true === empty($cancelGrantPic)) {
                throw new Exception('会员当前授权图片删除失败');
            }

            # 若到店记录有效,则软删除到店记录
            $arrive = new MemberFaceArrive($this->_companyId);
            $today = date('Y-m-d H:i:s', strtotime(date('Y-m-d')));
            $nextDay = date('Y-m-d H:i:s',
                    strtotime(date('Y-m-d', time() + 3600 * 24)));
            $arriveDel = $arrive->where(['member_code' => $val['member_code']])->where('create_time',
                            'between time', [$today, $nextDay])->find();
            if (false === empty($arriveDel)) {
                $arriveDelRes = $arriveDel->delete();  //软删除
            }
        }
        return true;
    }

    /**
     * 到店人脸识别列表 (当天) [OK]
     * @param $where type array 查询条件
     * @return 返回当天列表信息(object类型),否则返回false
     */
    public function getArriveMemberList($where) {

        # 查询当天到店会员列表,有则返回列表信息,否则返回false
        $arrive = new MemberFaceArrive($this->_companyId);
//        $arrive->restore(['arrive_id'=>19]);  //恢复软删除数据(pk)
        $today = date('Y-m-d', strtotime(date('Y-m-d')));
        $nextDay = date('Y-m-d', strtotime(date('Y-m-d', time() + 3600 * 24)));

        $arriveListRes = $arrive->where($where)->where('create_time',
                        'between time', [$today, $nextDay])->select();

        if (true === empty($arriveListRes)) {
            throw new \Exception('当天到店列表不存在');
        }
        return $arriveListRes;
    }

    /**
     * 到店会员签到(添加到签到表member_sign) [直接调用签到接口]
     * @param $addSignData array 插入数组
     * @param $companyCode string 公司名称
     * @return 受影响行数
     */
    public function addArriveMemberSign($addSignData, $companyCode) {
        # 软删除到店记录
        $where = [
        'member_code' => $addSignData['member_code'],
//            'store_id' => 'JJ_' . $addSignData['service_store'],
        'store_id' => $addSignData['service_store'],
        ];
        $cancelArriveRes = $this->deleteArriveFace($where);
        if (true === empty($cancelArriveRes)) {
            throw new \Exception('到店记录删除失败');
        }

        # 若会员到店记录删除成功,则进行签到
        $memberSign = new MemberSign($this->_companyId);
        $addArriveSignRes = $memberSign->memberCheckin($addSignData,
                $companyCode);  //调用已有接口
        ## 添加会员签到失败
        if (true === empty($addArriveSignRes)) {
            throw new \Exception('签到记录添加失败');
        }

        # 查询会员签到时间
        $getSignData = [
        'member_code' => $addSignData['member_code'],
        'service_store' => $addSignData['service_store'],
        ];
        $memberSignInfoRes = $this->getMemberSignInfo($getSignData);

        if (true === empty($memberSignInfoRes)) {
            throw new \Exception('当前会员今天签到记录为空');
        }
        return $memberSignInfoRes;
    }

    /**
     * 撤销到店人脸识别 [OK]
     * 1) 若是店员,当天到店记录软删除,修改授权标记位,下次不再进行到店识别[1,2,3]
     * 2) 若是图片与人不符,当天到店记录软删除,删除百度人脸库中图片,[1,2,3,4]
     * 3) 若是长得相似,当天到店记录软删除,不删除百度人脸库图片[1,2]
     * @param $data type array 查询条件 
     * @return $res type bool 成功返回true,否则false
     */
    public function cancelArriveMember($data) {

        $grant = new MemberFaceGrant($this->_companyId);
        $arrive = new MemberFaceArrive($this->_companyId);
        $cancelReason = Constants::$cancelArriveReasonArray[$data['cancel_reason']];

//        $arrive->restore(['arrive_id'=>61]);die; //恢复软删除数据
//        $grant->restore(['grant_id'=>82]);die; //恢复软删除数据
        ## 1)先修改到店表标记位,
        $today = date('Y-m-d H:i:s', strtotime(date('Y-m-d')));
        $nextDay = date('Y-m-d H:i:s',
                strtotime(date('Y-m-d', time() + 3600 * 24)));
        $findArriveObj = $arrive->where(['member_code' => $data['member_code'], 'store_id' => $data['store_id'],
                'grant_id' => $data['grant_id']])->where('create_time',
                        'between time', [$today, $nextDay])->find();
        $updateArriveRes = $arrive->where(['arrive_id' => $findArriveObj->arrive_id])->update([
        'cancel_reason' => $cancelReason]);
//        $updateArriveRes = $arrive->where(['member_code' => $data['member_code'],'store_id'=>$data['store_id'],'grant_id'=>$data['grant_id']])->where('create_time','between time',[$today,$nextDay])->update(['cancel_reason' => $cancelReason]); 
        if (true === empty($updateArriveRes)) {
            throw new \Exception('撤除的原因修改失败');
        }

        ## 2) 若为店员时,修改授权表标记位
        if ($cancelReason == 1) {

            $findGrantObj = $grant->where(['member_code' => $data['member_code'],
                    'is_staff' => 0, 'grant_id' => $data['grant_id']])->find();
            $updateGrantRes = $grant->where(['grant_id' => $findGrantObj->grant_id])->update([
            'is_staff' => 1]);
            if (true === empty($updateGrantRes)) {
                throw new \Exception('店员标记位修改失败');
            }
        }

        ## 2)若是图片与人不符合,2-1)先删除百度人脸库中图片(防止删除过程中继续识别)
        if ($cancelReason == 2) {

            ### 删除百度人脸库
            $uid = $data['member_code'] . '_' . $data['grant_id']; //member_code拼接grant_id
            $options['group_id'] = Config::get('BAIDU_FACE_GRANT_GROUP');
            $deleteUserRes = $this->_baiduService->deleteUser($uid, $options);

            if (false === empty($deleteUserRes['error_code'])) {  //百度人脸删除错误信息
                throw new \Exception($deleteUserRes['error_msg']);
            }

            ### 软删除授权表记录 [识别记录和百度人脸库图片一一对应]
            $grantDel = $grant->where(['member_code' => $data['member_code'], 'grant_id' => $data['grant_id']])->find();
            if (true === empty($grantDel)) {
                throw new \Exception('该会员暂未绑定授权图片');
            }
            $grantDelRes = $grantDel->delete();
            if (true === empty($grantDelRes)) {
                throw new \Exception('人脸照片删除失败');
            }
        }

        ## 3)并软删除当天到店记录,[只能有一条未删除的到店记录]
        $where = [
        'member_code' => $data['member_code'],
        'store_id' => $data['store_id'],
        ];
        $arriveDelRes = $this->deleteArriveFace($where);
        return true;    //撤销成功
    }

    /**
     * 软删除到店记录 [Ok]
     * @param $where array 查询条件:[门店ID,会员ID]
     * @param $isUseStoreId bool 是否使用门店ID
     * @return bool 成功返回true,失败返回false
     */
    public function deleteArriveFace($where, $isUseStoreId = true) {
        $arrive = new MemberFaceArrive($this->_companyId);
//        $arrive->restore(['arrive_id'=>30]);die; //恢复软删除数据
        $today = date('Y-m-d H:i:s', strtotime(date('Y-m-d')));
        $nextDay = date('Y-m-d H:i:s',
                strtotime(date('Y-m-d', time() + 3600 * 24)));

        # 软删除当天到店记录
        $delWhere = [
        'member_code' => $where['member_code'],
        'store_id' => $where['store_id']
        ];

        if (false === $isUseStoreId) {
            unset($delWhere['store_id']);
        }

        $arriveDel = $arrive->where($delWhere)->where('create_time',
                        'between time', [$today, $nextDay])->find();

        if (true === empty($arriveDel)) {
            throw new \Exception('会员今天到店记录为空');
        }
        $arriveDelRes = $arriveDel->delete();  //软删除

        if (true === empty($arriveDelRes)) {

            throw new \Exception('会员到店记录删除失败');
        }
        return $arriveDelRes;
    }

    /**
     * 获取会员当天签到的信息
     * @param $where 查询条件
     * @return 返回签到时间(string)
     */
    public function getMemberSignInfo($where) {
        $today = strtotime(date('Y-m-d'));
        $nextDay = strtotime(date('Y-m-d', time() + 3600 * 24));

        # 查询当天的签到记录(每天只有一条)
        $memberSign = new MemberSign($this->_companyId);
        $getMemberSignRes = $memberSign->where(['member_code' => $where['member_code'],
                'service_store' => $where['service_store']])->where('add_ts',
                        'between time', [$today, $nextDay])->order('add_ts',
                        'desc')->find();

        if (true === empty($getMemberSignRes)) {
            return false;
        }
        return date('Y-m-d H:i:s', $getMemberSignRes->add_ts); //签到时间
    }

    //+---------------------------------------------+
    //+--  下列方法用于addGrantMemberPic [重构]   --+ 
    //+---------------------------------------------+

    /**
     * 先上传图片到OSS,再将图片存入member_face
     * @param $obj OSS的bucket对象
     * @param $imgPath string 图片路径
     * @return $fileInfo array  [图片路径,图片路径]
     */
    public function ossUpload($obj, $imgPath, $companyId = 1) {

        $fileName = pathinfo($imgPath);
        $picUrl = $companyId . '/' . date('YmdH') . '/' . $fileName['basename']; //阿里云路径
        $content = file_get_contents("{$imgPath}");
        $filePath = $obj->fileUpload($picUrl, $content);
        return [
        'pic_url' => $picUrl,
        'file_path' => $filePath,
        ];
    }

    /**
     * 添加授权头像
     * @param $url string 图片服务器地址
     * @param $companyId int 集团ID
     * @param $memberCode string 集团会员ID
     * @param $path string 图片部分路径
     * @return json对象 
     */
    public function addMemberGrantFace($url, $companyId, $memberCode) {

        #上传到OSS,获取部分图片路径(全路径为:Oss域名 + Bucket + 当前部分路径 + 签名)
        $picUrlArr = explode('?', $url);
        $picPath = explode('/', $picUrlArr[0], 4)[3];

        # 获取带有签名的url
        $getSignData = [
        'bucket' => Config::get('ALIYUN_OSS_FACE_GRANT_BUCKET'),
        'object' => $picPath,
        ];
        $filePath = $this->_ossServiceGrant->getSignUrl($getSignData);
        # 人脸检测
        $detectRes = $this->_baiduService->detect(['url' => "{$filePath}"]);    //$url为图片地址

        if (false === empty($detectRes['result'][0])) {

            $completeness = $detectRes['result'][0]['qualities']['completeness'];

            # 判断人脸是否符合要求(完整度)
            if ((int) $completeness === 1) {

                # 当图片不存在授权表中,则添加到member_face_grant表中
                ## 同一用户可多次添加授权图片
                $grantData = [
                'member_code' => $memberCode,
                'grant_pic' => $picPath,
                'create_time' => date('Y-m-d H:i:s', time()),
                ];
                $grantRes = $this->addMemberGrantPic($grantData);

                # 进行人脸注册(分组:grant_group),uid=member_code拼接grant_id
                $addBaiduData = [
                'url' => $filePath,
                'uid' => $memberCode . '_' . $grantRes,
                'userInfo' => Config::get('BAIDU_FACE_GRANT_USER_INFO'),
                'groupId' => Config::get('BAIDU_FACE_GRANT_GROUP'),
                ];
                $addBaiduDbRes = $this->_baiduService->addBaiduFace($addBaiduData);

                ## 若存在错误码,则返回百度错误信息
                if ($addBaiduDbRes['error_code']) {
                    throw new \Exception($addBaiduDbRes['error_msg']);
                }
                return true; //添加会员授权图片成功
            }
            throw new \Exception('该照片人脸不够完整');
        }
        throw new \Exception('当前图片不合法,未捕获到人脸');
    }
    
     /**
     * 通过设备SN获取门店ID
     * @param array $data 查询条件
     * @return string/array  报错信息/查询结果(含门店ID)  
     */
    public function getStoreIdBySodSnName($data) {
        
        # 联表操作(关联3张表)
        $storeIdRes = $this->_sodSnModel
                ->alias('sodSn')
                ->field('sodSn.id, company_optometry_device.company_id company_id, store_optometry_device.id sod_id, store_optometry_device.store_id, store_optometry_device.cod_id, sodSn.sod_sn_name sod_sn_name')
                ->join('oms.store_optometry_device','sodSn.sod_id = store_optometry_device.id','LEFT')  //关联表名,关联条件,联表方式
                ->join('oms.company_optometry_device','company_optometry_device.id = store_optometry_device.cod_id','LEFT')
                ->where([
                    'company_optometry_device.company_id' => $data['companyId'],
                    'sodSn.sod_sn_name' => $data['sodSnName'],
                    'sodSn.delete_time' => ['exp' , 'is null']
                ])
                ->order(['sodSn.create_time'=>'desc'])
                ->find();
        if (true === empty($storeIdRes)) {
            return '当前设备SN不存在';
        }
        return $storeIdRes->toArray();  //查询结果数组(含门店ID)  
    }

}
